loader.efi——UEFI内核加载器
在UEFI系统上,loader.efi 加载内核。
boot1.efi(8) 用于在将 loader.efi 放置在UFS或ZFS文件系统中时加载它。或者,当使用 efibootmgr(8) 配置时,或者当直接放置为 uefi(8) 中描述的默认引导程序时,可以直接使用 loader.efi 。当使用 bsdinstall(8) 构建系统时,将直接使用 loader.efi 。
EFI BIOS提供了一个通用控制台。在 loader.efi 中,这是通过使用控制台变量指定“efi”来选择的。loader.efi 检查 8be4df61-93ca-11d2-aa0d-00e098032b8c-ConOut UEFI环境变量,以猜测“efi”控制台指向什么。 loader.efi 将其提示和菜单输出到ConOut指定的所有位置。然而,当存在多个控制台时,FreeBSD内核有一个限制。内核输出到所有配置的控制台。只有主控制台会从 rc(8) 系统获取日志消息,并提示输入 geli(8) 密码等信息。如果 loader.efi 首先找到一个视频设备,那么 loader.efi 告诉内核使用视频控制台作为主要设备。同样,如果串行设备位于ConOut列表中的第一个,则串行端口将是主控制台。
如果没有ConOut变量,则尝试串行和视频。 loader.efi 使用默认波特率的“efi”控制台用于视频(可能工作也可能不工作),使用“comconsole”用于COM1上的串行。内核将使用双控制台,如果检测到UEFI图形设备,则视频控制台为主控制台,否则串行控制台为主控制台。
在x86平台上,如果您希望在EFI BIOS不支持时将加载程序的输出重定向到串行端口,或者重定向到EFI BIOS将其输出重定向到的非串行端口,请将控制台设置为“comconsole”。默认端口为COM1,I/O地址为0x3f8。
comconsole_port 用于将其设置为不同的端口地址。comconsole_speed 用于设置串行端口的(默认值为9600)。如果您将控制台设置为 efi,comconsole ,您将在EFI控制台和串行端口上获得输出。如果这会导致字符加倍,请将控制台设置成“efi”,因为您的EFI BIOS已经重定向到串行端口。
如果EFI BIOS重定向串行端口,您可能需要告诉内核要使用哪个地址。EFI使用ACPI的UID来标识串行端口,但 loader.efi 没有ACPI解析器,因此无法将其转换为I/O端口。FreeBSD内核在解码ACPI资源之前会初始化其控制台。FreeBSD内核将查看 hw.uart.console 变量来设置其串行控制台。它的格式应该在 uart(4) 中描述,但没有。使用正确的端口地址将其设置为“io:0x3f8,br:115200”。PCI或内存映射端口超出了本手册页的范围。
在IBM PC兼容系统上,串行端口的分配如下:
xxxxxxxxxxWindows Name I/O Port Address Typical FreeBSD deviceCOM1 0x3f8 /dev/uart0COM2 0x2f8 /dev/uart1COM3 0x3e8 /dev/uart2COM4 0x2e8 /dev/uart3
虽然COM3和COM4可以变化。
使用引导标志(boot flags)设置主控制台。这些命令行参数为内核设置了相应的标志。这些标志可以通过将加载程序环境变量设置为 yes 或 no 来控制。引导标志可以在引导命令的命令行上设置。在内核中,RB_ 标志用于控制行为,有时是以特定于架构的方式,并被包含在内以帮助发现本文档中未涵盖的任何行为。
xxxxxxxxxxboot flag loader variable Kernel RB_ flag-a boot_askme RB_ASKNAME-c boot_cdrom RB_CDROM-d boot_ddb RB_KDB-r boot_dfltroot RB_DFLTROOT-D boot_multiple RB_MULTIPLE-m boot_mute RB_MUTE-g boot_gdb RB_GDB-h boot_serial RB_SERIAL-p boot_pause RB_PAUSE-P boot_probe RB_PROBE-s boot_single RB_SINGLE-v boot_verbose RB_VERBOSE
以下标志决定了主控制台:
xFlags Kernel Flags Kernel Consoles Primary Consolenone 0 Video Video-h RB_SERIAL Serial Serial-D RB_MULTIPLE Serial,Video Video-Dh RB_SERIAL|RB_MULTIPLE Serial,Video Serial
loader.efi 不实现查看 -P 功能,如果连接了键盘,则使用视频控制台,否则使用串行控制台。
loader.efi 在启动早期从EFI分区的 /efi/freebsd/loader.env 加载一些额外的变量。这里只能设置简单的变量。指定根文件系统可能很有用:
rootdev=disk0s1a内核必须解析固件内存映射表,以了解它可以使用什么内存。由于它必须分配内存才能做到这一点, loader.efi 确保在加载所有内容(内核、模块和元数据)后,有额外的可用内存,称为“slop”(溢出),供内核引导内存分配器。
默认情况下,amd64保留8MB。staging_slop 命令允许调整溢出大小。它只接受一个参数,即溢出的大小(以字节为单位)。
loader.efi 将内核加载到低于4GB的2MB内存中。它无法加载到固定地址,因为UEFI固件可能会在运行时保留任意内存供其使用。在FreeBSD 13.1之前,内核保留了旧的BIOS引导协议,即以2MB的速度加载。在启动这些内核之前,必须将其从加载位置复制到2MB。 copy_staging 命令用于为较旧的内核启用此复制。它接受一个参数,该参数可以是以下之一:
Arm64加载器从一开始就在“nocopy”模式下运行,因此该平台上没有 copy_staging 命令。Riscv、32位arm和arm64始终在任何2MB对齐的位置加载,因此不提供 copy_staging 。
注意。i386和amd64上的BIOS加载器将暂存区从物理地址2M开始,然后为低1G启用具有相同映射的分页。 loader.efi 的初始端口遵循将控制权移交给内核的相同方案,因为它避免了对加载器/内核移交协议和内核页表引导的修改。 这种方法与UEFI规范不兼容,并且实际上在许多板上造成了麻烦,因为UEFI固件可以自由使用任何内存来满足自己的需求。像 loader.efi 这样的应用程序必须只使用通过引导接口显式分配的内存。原始方式也可能破坏UEFI运行时接口数据。 最终,loader.efi 和内核得到了改进,以避免这个问题。
由于它在x86保护模式下执行,amd64版本的 loader.efi 容易因程序员错误和内存损坏而出现CPU故障。为了使调试此类故障更容易,amd64 loader.efi 可以提供故障发生时CPU状态的详细报告。
grab_faults 命令直接在IDT中安装故障处理程序,避免使用UEFI调试接口 EFI_DEBUG_SUPPORT_PROTOCOL.RegisterExceptionCallback() 。该接口可供UEFI环境中的高级调试器使用。 ungrab_faults 命令尝试卸载故障处理程序,将TSS和IDT CPU表恢复到安装前的状态。 fault 命令通过执行 ud2 处理器指令,在 loader.efi 环境中产生故障以进行测试。
/boot/loader.efi UEFI内核加载器在系统中的位置。
loader.efi 安装在ESP(EFI系统分区)的以下位置之一:
efi/boot/bootXXX.efi
任何EFI加载器的默认位置(有关用以替换“XXX”的值,请参阅 uefi(8) )。
efi/freebsd/loader.efi
专门为FreeBSD EFI 加载器保留的位置。
ESP装载点的默认位置记录在 hier(7) 中。
以下示例显示了如何在ESP上安装新的 loader.efi 。由于安装、设置和情况的多样性,确切的位置很复杂。在本节中,所有小写的路径都是Unix路径。所有大写的路径都是相对于ESP装载点的,尽管它们在您的系统上可能显示为小写,因为ESP的FAT文件系统不区分大小写。
找到ESP,它有自己的分区类型“efi”:
xxxxxxxxxx# gpart show nda0=> 40 7501476448 nda0 GPT (3.5T) 40 614400 1 efi (300M) 614440 7500862048 2 freebsd-zfs (3.5T)此系统上的ESP名称为 nda0p1 。默认情况下,这将被挂载在 /boot/efi 上。检查:
# mount | grep nda0p1/dev/nda0p1 on /boot/efi (msdosfs, local)如果它没有挂载,你需要使用以下命令挂载它:
# mount -t msdosfs /dev/nda0p1 /boot/efiefibootmgr(8) 报告了我们从何处启动。
x# efibootmgr -vBoot to FW : falseBootCurrent: 0001Timeout : 2 secondsBootOrder : 0000, 0001, 0003, 0004, 0005, 0006, 0001, 0008, 000A, 000B, 000C, 000E, 0007...+Boot0001* FreeBSD ZPOOL HD(1,GPT,b5d0f86b-265d-1e1b-18aa-0ed55e1e73bd,0x28,0x96000)/File(\EFI\FREEBSD\LOADER.EFI)nda0p1:/EFI/FREEBSD/LOADER.EFI /boot/efi//EFI/FREEBSD/LOADER.EFI...
通常有几个选项,具体取决于BIOS。我们启动的条目在行首标有“+”,如上所示。因此,在这种情况下,此固件使用ESP的 /EFI/FREEBSD/LOADER.EFI 。通常情况下,它将是UEFI“默认”加载程序,这因架构而异。
xxxxxxxxxxArchitecture Default Pathamd64 /EFI/BOOT/BOOTX64.EFIarm /EFI/BOOT/BOOTARM.EFIarm64 /EFI/BOOT/BOOTAA64.EFIi386 /EFI/BOOT/BOOTIA32.EFIriscv /EFI/BOOT/BOOTRISCV64.EFI
但是,必须小心:一些多引导环境依赖于特殊的 bootXXX.efi 才能运行。在更新 bootXXX.efi 文件之前,请确保它是FreeBSD引导加载程序,然后再进行更新:
# strings /boot/efi/EFI/BOOT/BOOTX64.EFI | grep FreeBSD | grep EFI FreeBSD/amd64 EFI loader, Revision 3.0bsdinstall(8) 将 loader.efi 复制到默认名称(如果以前没有)。在更新之前检查它们是否是副本(使用上表替换X64):
# cmp /boot/efi/EFI/FREEBSD/LOADER.EFI /boot/efi/EFI/BOOT/BOOTX64.EFI复制加载器:
# cp /boot/loader.efi /boot/efi/EFI/FREEBSD/LOADER.EFI用正确的路径替换示例中的所有大写部分。
如果ESP路径为 /FREEBSD/LOADER.EFI 和 LOADER.EFI 和 BOOTX64.EFI 在cmp步骤中是相同的,将加载器复制到默认位置:
# cp /boot/loader.efi /boot/efi/EFI/BOOT/BOOTX64.EFI最后,如果您挂载了ESP,您可能希望卸载它。
# umount /boot/efixxxxxxxxxxloader(8), uefi(8)
非x86串行控制台处理更令人困惑,文档也更少。
有时,当串行端口速度未设置时,会使用9600。其他时候,由于速度与默认值保持不变,结果通常为115200。
U-Boot实现了UEFI标准的一个子集。某些版本不支持获取加载程序变量,因此 efibootmgr 可能无法工作。此外,armv7或riscv不支持 efibootmgr 。在这些情况下,用户必须了解引导的内容才能正确更新(在大多数情况下,它将是FreeBSD路径和UEFI默认路径,因此如果那里有加载器,只需将 loader.efi 复制到那里)。通常,在这些嵌入式情况下,只有一个 .efi 文件( loader.efi 或 loader.efi 的副本)。此文件的路径通常是上面的默认可移动路径。
在UEFI上引导多个操作系统的管理差异很大,因此在更新UEFI默认加载程序时要格外小心。
旧的、现已过时的 boot1.efi 在FreeBSD 10及更早版本中作为 bootx64.efi 安装。由于它的功能非常有限,我们默认创建了非常小的ESP。现代 loader.efi 不适合。但是,如果旧的 boot1.efi 仍然有效,则无需对其进行更新,因为它会将 boot/boot/loader.efi 与进行installworld更新的副本链接起来。